package com.bestcem.xm.survey.grpc.service;

import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bestcem.xm.survey.convert.grpc.SurveyGrpcConvertMapper;
import com.bestcem.xm.survey.entity.*;
import com.bestcem.xm.survey.grpc.client.DeliverGrpcClient;
import com.bestcem.xm.survey.grpc.client.MemberGrpcClient;
import com.bestcem.xm.survey.grpc.client.QdesGrpcClient;
import com.bestcem.xm.survey.grpc.client.UserGrpcClient;
import com.bestcem.xm.survey.grpc.client.dto.*;
import com.bestcem.xm.survey.grpc.v1.services.*;
import com.bestcem.xm.survey.mapper.*;
import com.bestcem.xm.survey.service.SurveyColumnService;
import com.bestcem.xm.survey.service.SurveyCommonService;
import com.bestcem.xm.survey.service.SurveyExtParameterService;
import com.bestcem.xm.survey.service.SurveyRespondentExportRecordService;
import com.bestcem.xm.survey.util.SurveyConstant;
import com.google.protobuf.Timestamp;
import com.google.protobuf.util.Timestamps;
import io.grpc.stub.StreamObserver;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.devh.boot.grpc.server.service.GrpcService;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ObjectUtils;

import java.time.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static com.bestcem.xm.survey.enums.ColumnsMetaSortEnum.*;
import static com.bestcem.xm.survey.enums.RespondentDataStatusEnum.RESPONDENT_DATA_STATUS_FORMAL;
import static com.bestcem.xm.survey.enums.RespondentStatusEnum.RESPONDENT_STATUS_FINISH;
import static com.bestcem.xm.survey.grpc.service.GrpcConstant.FAIL_STATUS;
import static com.bestcem.xm.survey.grpc.service.GrpcConstant.SUCCESS_STATUS;
import static com.bestcem.xm.survey.util.SurveyConstant.COLUMNS_META_SORT;

/**
 * 获取基础survey接口
 *
 * @author xiaoshu.shao <xiaoshu.shao@idiaoyan.com>
 * @version V1.0
 * @date 2022/9/21
 */
@GrpcService
@Slf4j
@RequiredArgsConstructor
public class BpSurveyTicketGrpcService extends BpSurveyTicketServiceGrpc.BpSurveyTicketServiceImplBase {
    private @NonNull SurveyGrpcConvertMapper grpcConvertMapper;
    private @NonNull SurveyRespondentMapper surveyRespondentMapper;
    private @NonNull SurveyRespondentDataMapper surveyRespondentDataMapper;
    private @NonNull SurveyRespondentModifyLogMapper surveyRespondentModifyLogMapper;
    private @NonNull SurveyRespondentExtraMapper surveyRespondentExtraMapper;
    private @NonNull SurveyColumnService surveyColumnService;
    private @NonNull MemberGrpcClient memberGrpcClient;
    private @NonNull SurveyRespondentExportRecordService exportRecordService;
    private @NonNull UserGrpcClient userGrpcClient;
    private @NonNull DeliverGrpcClient deliverGrpcClient;
    private @NonNull SurveyRespondentWeixinMapper surveyRespondentWeixinMapper;
    private @NonNull SurveyColumnMapper surveyColumnMapper;
    private @NonNull QdesGrpcClient qdesGrpcClient;
    private @NonNull SurveyExtParameterService surveyExtParameterService;
    private @NonNull SurveyGrpcConvertMapper surveyGrpcConvertMapper;
    private @NonNull SurveyCommonService surveyCommonService;

    @Override
    public void getFinishedRespondentCountByTimeRange(GetFinishedRespondentCountByTimeRangeRequest request,
                                                      StreamObserver<GetFinishedRespondentCountByTimeRangeResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getFinishedRespondentCountByTimeRange");
        GetFinishedRespondentCountByTimeRangeResponse.Builder response = GetFinishedRespondentCountByTimeRangeResponse.newBuilder();

        String projectId = request.getProjectId();
        Timestamp startTime = request.getStartTime();
        Timestamp endTime = request.getEndTime();
        if (StringUtils.isEmpty(projectId) || startTime.getSeconds() < 0
                || endTime.getSeconds() < 0) {
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }
        // 获取指定范围内的有效答卷数的数量
        Long count = surveyRespondentMapper.selectCount(new LambdaQueryWrapper<SurveyRespondent>()
                .eq(SurveyRespondent::getProjectId, projectId)
                .eq(SurveyRespondent::getDataStatus, RESPONDENT_DATA_STATUS_FORMAL.getValue())
                .eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_FINISH.getValue())
                .ge(SurveyRespondent::getFinishTime, googleTimestampToLocalDateTime(startTime))
                .lt(SurveyRespondent::getFinishTime, googleTimestampToLocalDateTime(endTime)));

        response.setCount(Integer.parseInt(String.valueOf(count)));
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    @Override
    public void getRespondentBySeq(GetRespondentBySeqRequest request, StreamObserver<GetRespondentBySeqResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getRespondentBySeq");
        GetRespondentBySeqResponse.Builder response = GetRespondentBySeqResponse.newBuilder();

        long seq = request.getSeq();
        boolean answer = request.getAnswer();
        boolean extra = request.getExtra();
        boolean parse = request.getParse();
        String field = request.getField();
        boolean member = request.getMember();
        String userId = request.getUserId();
        String projectId = request.getProjectId();
        if (StringUtils.isEmpty(projectId)) {
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            log.error("Project is null");
            return;
        }
        SurveyRespondent rspd = surveyRespondentMapper.selectOne(new LambdaQueryWrapper<SurveyRespondent>()
                .eq(SurveyRespondent::getProjectId, projectId)
                .eq(SurveyRespondent::getSeq, seq).last("limit 1"));
        if (rspd == null) {
            log.error("surveyRespondents is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }
        String resAnswer;
        String resExt;
        SurveyRespondentExtra rspdExtra;
        MemberNode memberNode;
        List<String> memberTags = new ArrayList<>();

        if (answer) {
            SurveyRespondentData rspdData = surveyRespondentDataMapper.selectOne(new LambdaQueryWrapper<SurveyRespondentData>()
                    .eq(SurveyRespondentData::getProjectId, projectId)
                    .eq(SurveyRespondentData::getRid, rspd.getId()).last("limit 1"));
            if (rspdData == null) {
                log.error("surveyRespondentData is null");
                response.setStatus(FAIL_STATUS);
                responseObserver.onNext(response.build());
                responseObserver.onCompleted();
                return;
            }
            JSONObject answers = JSONObject.parseObject(rspdData.getAnswers());
            // 获取外部参数
            JSONArray ext = surveyColumnService.parseCustomAnswers(rspdData.getCustomAnswers());
            Long mLogCount = surveyRespondentModifyLogMapper.selectCount(new LambdaQueryWrapper<SurveyRespondentModifyLog>()
                    .eq(SurveyRespondentModifyLog::getProjectId, projectId)
                    .eq(SurveyRespondentModifyLog::getSeq, seq));
            if (mLogCount > 0) {
                List<Integer> gids = new ArrayList<>();
                List<SurveyRespondentModifyLog> modifyLogs = surveyRespondentModifyLogMapper.selectList(new LambdaQueryWrapper<SurveyRespondentModifyLog>()
                        .eq(SurveyRespondentModifyLog::getProjectId, projectId)
                        .eq(SurveyRespondentModifyLog::getSeq, seq)
                        .orderByAsc(SurveyRespondentModifyLog::getCreatedDt));
                for (SurveyRespondentModifyLog log : modifyLogs) {
                    if (!gids.contains(log.getGid())) {
                        for (String k : answers.keySet()) {
                            if (log.getGid().equals(Integer.parseInt(k))) {
                                answers.put(k, JSONObject.parseObject(log.getOldAnswer()).get(log.getGid().toString()));
                            }
                        }
                        rspdData.setAnswers(answers.toJSONString());
                        gids.add(log.getGid());
                    }
                }
            }
            if (parse) {
                JSONObject sensitiveMap = surveyColumnService.getUidsSensitives(projectId, userId);
                JSONObject params = new JSONObject();
                params.put("projectId", projectId);
                params.put("version", rspd.getVersion());
                params.put("answers", rspdData.getAnswers());
                params.put("seq", seq);
                params.put("with_attach", true);
                params.put("sensitive_map", sensitiveMap);
                params.put("whole_qinfo", false);
                params.put("id", rspd.getId());
                params.put("status", rspd.getStatus());

                Object ans = surveyColumnService.parseAnswers(params, null);
                resAnswer = JSON.toJSONString(ans);
            } else {
                resAnswer = rspdData.getAnswers();
            }
            response.setAnswer(resAnswer);
            resExt = ext.toJSONString();
            response.setExtParam(resExt);
        }
        if (extra) {
            rspdExtra = surveyRespondentExtraMapper.selectOne(new LambdaQueryWrapper<SurveyRespondentExtra>()
                    .eq(SurveyRespondentExtra::getRid, rspd.getId()).last("limit 1"));
            if (rspdExtra != null) {
                response.setExtra(grpcConvertMapper.toRespondentExtra(rspdExtra));
            }
        }

        if (member && StringUtils.isNotEmpty(rspd.getMemberId())) {
            MemberDto memberDto = memberGrpcClient.getMemberById(rspd.getMemberId());
            if (memberDto != null) {
                MemberNode.Builder builder = MemberNode.newBuilder();
                if (StrUtil.isNotBlank(memberDto.getName())) {
                    builder.setName(memberDto.getName());
                }
                if (StrUtil.isNotBlank(memberDto.getGender())) {
                    builder.setGender(memberDto.getGender());
                }
                if (StrUtil.isNotBlank(memberDto.getPhone())) {
                    builder.setPhone(memberDto.getPhone());
                }
                if (StrUtil.isNotBlank(memberDto.getEmail())) {
                    builder.setEmail(memberDto.getEmail());
                }
                memberNode = builder.build();

                //memberNode = MemberNode.newBuilder().setName(memberDto.getName()).setGender(memberDto.getGender())
                //        .setPhone(memberDto.getMobile()).setEmail(memberDto.getEmail()).build();
                List<MemberDto.LabelValueDto> labelValuesList = memberDto.getLabelValuesList();
                if (CollectionUtils.isNotEmpty(labelValuesList)) {
                    for (MemberDto.LabelValueDto labelValueDto : labelValuesList) {
                        memberTags.add(labelValueDto.getValue());
                    }
                }
                response.setMember(memberNode);
            }
        }
        Respondent respondent = grpcConvertMapper.toRespondent(rspd);
        response.setRespondent(respondent);
        response.addAllMemberTags(memberTags);
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    @Override
    public void getFilledRespondentInfo(GetFilledRespondentInfoRequest request, StreamObserver<GetFilledRespondentInfoResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getFilledRespondentInfo");
        GetFilledRespondentInfoResponse.Builder response = GetFilledRespondentInfoResponse.newBuilder();

        String projectId = request.getProjectId();
        long seq = request.getSeq();
        String userId = request.getUserId();
        String orgId = request.getOrgId();

        if (StringUtils.isEmpty(projectId)) {
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }
        SurveyRespondent rspd = surveyRespondentMapper.selectOne(new LambdaQueryWrapper<SurveyRespondent>()
                .eq(SurveyRespondent::getProjectId, projectId)
                .eq(SurveyRespondent::getSeq, seq).last("limit 1"));
        if (rspd == null) {
            log.error("The rspd is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }

        if (rspd.getBeginTime() != null && rspd.getFinishTime() != null) {
            response.setEndTime(localDateTimetoGoogleTimestamp(rspd.getFinishTime()));
            response.setCostTime(this.secondsConvert2Hms(Duration.between(rspd.getBeginTime(), rspd.getFinishTime()).getSeconds()));
        }
        if (rspd.getBeginTime() != null) {
            response.setStartTime(localDateTimetoGoogleTimestamp(rspd.getBeginTime()));
        }
        if (StringUtils.isNotEmpty(rspd.getUserAgent())) {
            Map<String, String> userAgent = this.parseUserAgent(rspd.getUserAgent());
            response.setBrowser(userAgent.get("browser"));
            response.setOs(userAgent.get("os"));
        }
        if (StringUtils.isNotEmpty(rspd.getGroupId())) {
            GroupDto groupDto = userGrpcClient.getGroup(rspd.getGroupId());
            response.setGroup(groupDto == null ? "全国" : groupDto.getTitle());
        } else {
            response.setGroup("全国");
        }

        if (StringUtils.isNotEmpty(rspd.getMemberId())) {
            MemberDto memberDto = memberGrpcClient.getMemberById(rspd.getMemberId());
            if (memberDto != null) {
                boolean hasDisplay = memberGrpcClient.checkDisplayRangePermission(orgId, userId, memberDto.getId());
                response.setMemberName(memberDto.getName());
                String mEmail;
                String mMobile;
                String mWechat = StringUtils.isEmpty(memberDto.getOpenId()) ? "空" : memberDto.getOpenId();
                String mAlipay = StringUtils.isEmpty(memberDto.getAlipayUserId()) ? "空" : memberDto.getAlipayUserId();
                if (!hasDisplay) {
                    mEmail = StringUtils.isEmpty(memberDto.getEmail()) ? "空" : surveyColumnService.hideEmail(memberDto.getEmail(), true);
                    mMobile = StringUtils.isEmpty(memberDto.getMobile()) ? "空" : surveyColumnService.hideMobile(memberDto.getMobile(), true);
                } else {
                    mEmail = StringUtils.isEmpty(memberDto.getEmail()) ? "空" : memberDto.getEmail();
                    mMobile = StringUtils.isEmpty(memberDto.getMobile()) ? "空" : memberDto.getMobile();
                }
                response.setMemberInfo(String.format("%s/%s/%s/%s", mMobile, mEmail, mWechat, mAlipay));
            }
        }
        if (StringUtils.isNotEmpty(rspd.getDeliverId())) {
            DeliverDto deliver = deliverGrpcClient.getDeliver(rspd.getDeliverId());
            if (deliver != null) {
                response.setDeliverSource(deliver.getName());
                response.setDeliverChannel(surveyColumnService.getdeliverChannelName(deliver.getChannel().toString()));
            }
        }
        if (StringUtils.isNotEmpty(rspd.getQuotaCheckitemList())) {
            response.addAllQuotaCheckItems(JSONArray.parseArray(rspd.getQuotaCheckitemList()).toJavaList(String.class));
        }
        if (rspd.getCreatedDt() != null) {
            response.setCreateTime(localDateTimetoGoogleTimestamp(rspd.getCreatedDt()));
        }
        if (StringUtils.isNotEmpty(rspd.getRspdAttachmentId())) {
            response.setAttachmentId(rspd.getRspdAttachmentId());
        }
        if (StringUtils.isNotEmpty(rspd.getRspdDataId())) {
            response.setDataId(rspd.getRspdDataId());
        }
        if (StringUtils.isNotEmpty(rspd.getRspdExtraId())) {
            response.setExtraId(rspd.getRspdExtraId());
        }
        if (StringUtils.isNotEmpty(rspd.getRspdLocationId())) {
            response.setLocationId(rspd.getRspdLocationId());
        }
        if (StringUtils.isNotEmpty(rspd.getPersonId())) {
            response.setUserId(rspd.getPersonId());
        }
        if (StringUtils.isNotEmpty(rspd.getRspdWeixinId())) {
            response.setWeixinId(rspd.getRspdWeixinId());
        }
        SurveyRespondentWeixin wxInfo = surveyRespondentWeixinMapper.selectOne(new LambdaQueryWrapper<SurveyRespondentWeixin>()
                .eq(SurveyRespondentWeixin::getRid, rspd.getId()).last("limit 1"));
        if (wxInfo != null) {
            response.setWxUserInfo(JSONObject.toJSONString(wxInfo));
        } else {
            response.setWxUserInfo("{}");
        }
        response.setIdySurveyUnionId(rspd.getIdySid() == null ? "" : rspd.getIdySid());
        response.setComment("");
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    @Override
    public void getAnswerScoreBySeq(GetAnswerScoreBySeqRequest request, StreamObserver<GetAnswerScoreBySeqResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getAnswerScoreBySeq");
        GetAnswerScoreBySeqResponse.Builder response = GetAnswerScoreBySeqResponse.newBuilder();
        String projectId = request.getProjectId();
        long seq = request.getSeq();

        if (StringUtils.isEmpty(projectId)) {
            log.info("[BpSurveyGrpcService] projectId is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }

        JSONObject js = new JSONObject();
        js.put("seq", seq);
        js.put("projectId", projectId);
        SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(
                new QueryWrapper<SurveyRespondent>().lambda().eq(SurveyRespondent::getProjectId, projectId)
                        .eq(SurveyRespondent::getSeq, seq).last("limit 1"));
        if (ObjectUtil.isNull(surveyRespondent)) {
            log.info("[BpSurveyGrpcService] surveyRespondent is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }
        js.put("version", surveyRespondent.getVersion());
        js.put("id", surveyRespondent.getId());
        js.put("status", surveyRespondent.getStatus());

        Double score = surveyColumnService.getScoreByAnswer(js);
        if (score != null) {
            response.setScore(score);
        }
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    @Override
    public void getColumnsBySurveyId(GetColumnsBySurveyIdRequest request, StreamObserver<GetColumnsBySurveyIdResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getColumnsBySurveyId");
        GetColumnsBySurveyIdResponse.Builder response = GetColumnsBySurveyIdResponse.newBuilder();
        String surveyId = request.getSurveyId();
        if (StringUtils.isEmpty(surveyId)) {
            log.info("[BpSurveyGrpcService] surveyId is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }

        List<SurveyColumn> surveyColumns = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>()
                .eq(SurveyColumn::getProjectId, surveyId)
                .orderByAsc(SurveyColumn::getCtype));
        List<Column> columns = new ArrayList<>();
        if (CollectionUtils.isNotEmpty(surveyColumns)) {
            for (SurveyColumn surveyColumn : surveyColumns) {
                columns.add(grpcConvertMapper.toColumn(surveyColumn));
            }
        }
        response.addAllColumns(columns);
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    @Override
    public void getAllRespondentData(GetAllRespondentDataRequest request, StreamObserver<GetAllRespondentDataResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getAllRespondentData");
        GetAllRespondentDataResponse.Builder response = GetAllRespondentDataResponse.newBuilder();
        String projectId = request.getProjectId();
        String usrId = request.getUserId();
        long seq = request.getSeq();
        if (StringUtils.isEmpty(projectId)) {
            log.info("[BpSurveyGrpcService] surveyId is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }

        JSONObject data = genRspdColumInfo(projectId, usrId, seq);
        String ret = JSON.toJSONString(data, SerializerFeature.SortField, SerializerFeature.MapSortField);
        response.setData(ret);
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    @Override
    public void getContactBySeq(GetContactBySeqRequest request, StreamObserver<GetContactBySeqResponse> responseObserver) {
        log.info("[BpSurveyGrpcService] now getContactBySeq");
        GetContactBySeqResponse.Builder response = GetContactBySeqResponse.newBuilder();
        String projectId = request.getProjectId();
        long seq = request.getSeq();
        if (StringUtils.isEmpty(projectId)) {
            log.info("[BpSurveyGrpcService] projectId is null");
            response.setStatus(FAIL_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }
        JSONObject contact = rspdFindContact(seq, projectId);
        if (ObjectUtils.isEmpty(contact)) {
            response.setContact(ContactNode.newBuilder()
                    .setEmail("")
                    .setPhone("")
                    .build());
            response.setStatus(SUCCESS_STATUS);
            responseObserver.onNext(response.build());
            responseObserver.onCompleted();
            return;
        }
        response.setContact(ContactNode.newBuilder()
                .setEmail(contact.getString("email"))
                .setPhone(contact.getString("phone"))
                .build());
        response.setStatus(SUCCESS_STATUS);
        responseObserver.onNext(response.build());
        responseObserver.onCompleted();
    }

    private JSONObject rspdFindContact(long seq, String projectId) {
        // 从答卷数据中查询手机邮箱等联系信息
        //    @params seq: 答卷seq
        //    @return {'phone': 'xxxx', 'email': 'xxxx'}
        SurveyRespondent rspd = surveyRespondentMapper.selectOne(
                new QueryWrapper<SurveyRespondent>().lambda().eq(SurveyRespondent::getSeq, seq)
                        .eq(SurveyRespondent::getProjectId, projectId).last("limit 1"));
        if (ObjectUtil.isNull(rspd)) {
            log.error("[rspdFindContact] rspd is null, projectId = {}, seq = {}", projectId, seq);
            return null;
        }
        SurveyRespondentData rspdData = surveyColumnService.getRespondentDataByRid(rspd.getId(), projectId);
        JSONObject params = new JSONObject();

        params.put("projectId", rspd.getProjectId());
        params.put("version", rspd.getVersion());
        params.put("answers", rspdData.getAnswers());
        params.put("whole_qinfo", true);
        params.put("with_attach", false);
        params.put("sensitive_map", new JSONObject());
        params.put("id", rspd.getId());
        params.put("seq", seq);
        params.put("status", rspd.getStatus());

        Object ans = surveyColumnService.parseAnswers(params, null);
        JSONObject qStruct = (JSONObject) ans;
        JSONObject typeMap = new JSONObject();
        typeMap.put("info_phone", "phone");
        typeMap.put("info_email", "email");
        JSONObject result = new JSONObject();
        for (JSONObject q : surveyCommonService.walkQstructQuestions(qStruct.getJSONObject("data"))) {
            String customQType = q.getString("custom_qtype");
            if (typeMap.containsKey(customQType)) {
                Object answers = q.get("answers");
                if (answers instanceof String) {
                    result.put(typeMap.getString(customQType), answers);
                } else if (answers instanceof JSONObject && ((JSONObject) answers).containsKey("blank")) {
                    result.put(typeMap.getString(customQType), ((JSONObject) answers).get("blank"));
                }
            }
        }
        return result;
    }

    private JSONObject genRspdColumInfo(String projectId, String uid, long seq) {
        log.info("[Survey] genRspdColumInfo projectId={},  uid={},  seq={}", projectId, uid, seq);
        // 生成seq 对应的column中所有答卷数据，包含column中状态为删除的数据
        if (StringUtils.isEmpty(projectId) || StringUtils.isEmpty(uid)) {
            return new JSONObject();
        }
        JSONObject sensitiveMap = surveyColumnService.getUidsSensitives(projectId, uid);
        SurveyRespondent rspd = surveyRespondentMapper.selectOne(
                new QueryWrapper<SurveyRespondent>().lambda().eq(SurveyRespondent::getProjectId, projectId)
                        .eq(SurveyRespondent::getSeq, seq).last("limit 1"));
        if (ObjectUtil.isNull(rspd)) {
            return new JSONObject();
        }
        SurveyRespondentWeixin wxInfo = surveyRespondentWeixinMapper.selectOne(new LambdaQueryWrapper<SurveyRespondentWeixin>()
                .eq(SurveyRespondentWeixin::getRid, rspd.getId()).last("limit 1"));

        rspd.setWxInfo(wxInfo);
        List<SurveyColumn> cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_META_FRONT.getValue()).gt(SurveyColumn::getSortNo, 9)
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, SurveyColumn> levelsFront = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            levelsFront.put(column.getId(), column);
        }
        // 前半部分元数据字段
        cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_META_FRONT.getValue())
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, String> columnsFront = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            columnsFront.put(column.getId(), column.getCol());
        }
        // 问题字段
        cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_QUESTION.getValue())
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, SurveyColumn> columnsQuestion = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            columnsQuestion.put(column.getId(), column);
        }
        // 后半部分元数据字段
        cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_META_BEHIND.getValue())
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, String> columnsBehind = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            columnsBehind.put(column.getId(), column.getCol());
        }
        // 联系人字段
        cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_MEMBER.getValue())
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, String> columnsMember = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            columnsMember.put(column.getId(), column.getCol());
        }
        // 外部参数字段
        cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_EXTERNAL.getValue())
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, String> columnsExt = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            columnsExt.put(column.getId(), column.getCol());
        }
        // 总分字段
        cursor = surveyColumnMapper.selectList(new LambdaQueryWrapper<SurveyColumn>().eq(SurveyColumn::getProjectId, projectId)
                .eq(SurveyColumn::getCtype, COLUMN_CTYPE_SCORE.getValue())
                .orderByAsc(SurveyColumn::getCtype)
                .orderByAsc(SurveyColumn::getSortNo));
        TreeMap<String, String> columnsScore = new TreeMap<>();
        for (SurveyColumn column : cursor) {
            columnsScore.put(column.getId(), column.getCol());
        }
        ProjectDto project = surveyGrpcConvertMapper.toProjectDto(qdesGrpcClient.getProject(projectId));
        if (project == null) {
            log.error("The project is null, id = {}", projectId);
            return new JSONObject();
        }

        List<QstructsDto> qstructList = getQstructList(projectId, project.getReleaseVer());
        if (CollectionUtils.isEmpty(qstructList)) {
            log.error("The qstructList is null, projectId = {}, version = {}", projectId, project.getReleaseVer());
            return new JSONObject();
        }
        // 获取最新的外部参数
        SurveyExtParameter extParameters = surveyExtParameterService.getExtParam(projectId, null, Boolean.FALSE);
        Map<String, Integer> extparametersTypeMap = new HashMap<>();
        if (extParameters != null && StringUtils.isNotEmpty(extParameters.getData()) && !StrUtil.equals("[]", extParameters.getData())) {
            for (JSONObject data : JSONArray.parseArray(extParameters.getData(), JSONObject.class)) {
                extparametersTypeMap.put(data.getString("name"), exportRecordService.extParamsTypeFormat(data.get("dtype")));
            }
        }
        // 外部参数改版获取外部参数
        // 外部参数改版，如果新的外部参数和已经存在的外部参数同名类型不同，以新的外部参数类型为准
        JSONObject paramJs = new JSONObject();
        paramJs.put("survey_id", project.getId());
        List<DeliverExtParamsDto> deliverExtParamsList = deliverGrpcClient.listDeliverExtparams(paramJs);
        if (CollectionUtils.isNotEmpty(deliverExtParamsList)) {
            for (DeliverExtParamsDto deliverExtParam : deliverExtParamsList) {
                extparametersTypeMap.put(deliverExtParam.getExtparamName(), deliverExtParam.getExtparamType());
            }
        }

        QstructsDto qstruct;
        if (rspd.getVersion().equals(project.getReleaseVer())) {
            qstruct = qstructList.get(0);
        } else {
            List<QstructsDto> tmpQstructList = getQstructList(projectId, rspd.getVersion());
            qstruct = tmpQstructList.get(0);
        }
        project.setQstruct(JSONObject.toJSONString(qstruct));

        SurveyRespondentData rspdData = surveyColumnService.getRespondentDataByRid(rspd.getId(), project.getId());
        List<JSONObject> questionList = new ArrayList<>();

        exportRecordService.getQstructQuestions(JSONObject.parseObject(qstruct.getData()), new HashMap<>(), questionList);
        Map<String, JSONObject> gidQuestionMap = new HashMap<>();
        for (JSONObject q : questionList) {
            gidQuestionMap.put(q.getString("gid"), q);
        }

        // 获取当前用户是否有查看联系方式权限
        Map<Integer, Boolean> mdisplayRangePerm = new HashMap<>();
        if (StringUtils.isNotEmpty(uid)) {
            SurveyConstant.MEMBER_MTYPE_DICT.keySet().forEach(v -> mdisplayRangePerm.put(v, memberGrpcClient.checkMemberDisplayRangeByMtype(project.getCompanyId(), uid, v)));
        }
        JSONObject result = new JSONObject();
        COLUMNS_META_SORT.forEach(tmp -> {
            if (tmp.equals(COLUMN_CTYPE_META_FRONT.getValue())) {
                List<Map<String, Object>> subItems = new ArrayList<>();
                subItems = exportRecordService.genMetaFront(subItems, rspd, new ArrayList<>(levelsFront.values()),
                        new ArrayList<>(columnsFront.values()));
                // 删除第一条数据
                subItems.remove(0);

                TreeMap<String, Object> columns_front = new TreeMap<>();
                columns_front.putAll(columnsFront);
                List<String> keys = new ArrayList<>(levelsFront.keySet());
                for (String le : keys) {
                    columns_front.put(le, levelsFront.get(le));
                }
                contactResult(subItems, columns_front, result, gidQuestionMap);
            } else if (tmp.equals(COLUMN_CTYPE_QUESTION.getValue())) {
                List<Map<String, Object>> subItems = exportRecordService.genQuestionInfo(new ArrayList<>(), rspd,
                        new ArrayList<>(columnsQuestion.values()), project, sensitiveMap, rspdData, null);
                contactResult(subItems, columnsQuestion, result, gidQuestionMap);
            } else if (tmp.equals(COLUMN_CTYPE_META_BEHIND.getValue())) {
                List<Map<String, Object>> subItems = exportRecordService.genMetaBehind(new ArrayList<>(), rspd, project,
                        new ArrayList<>(columnsBehind.values()));
                contactResult(subItems, columnsBehind, result, gidQuestionMap);
            } else if (tmp.equals(COLUMN_CTYPE_SCORE.getValue())) {
                List<Map<String, Object>> subItems = exportRecordService.genScoreByCalc(new ArrayList<>(), rspdData,
                        qstructList, new ArrayList<>(columnsScore.values()));
                contactResult(subItems, columnsScore, result, gidQuestionMap);
            } else if (tmp.equals(COLUMN_CTYPE_MEMBER.getValue())) {
                List<Map<String, Object>> subItems = exportRecordService.genMember(new ArrayList<>(), rspd,
                        new ArrayList<>(columnsMember.values()), mdisplayRangePerm);
                contactResult(subItems, columnsMember, result, gidQuestionMap);
            } else if (tmp.equals(COLUMN_CTYPE_EXTERNAL.getValue())) {
                JSONObject enumExtParams = surveyCommonService.getEnumExtParams(project.getCompanyId(), null);
                List<Map<String, Object>> subItems = exportRecordService.genExtExparams(new ArrayList<>(), rspdData,
                        new ArrayList<>(columnsExt.values()), extparametersTypeMap, enumExtParams);
                contactResult(subItems, columnsExt, result, gidQuestionMap);
            }
        });

        return result;
    }

    private void contactResult(List<Map<String, Object>> items, TreeMap<String, ?> columns, JSONObject results,
                               Map<String, JSONObject> gidQuestionMap) {
        if (CollectionUtils.isEmpty(items) || columns == null || columns.isEmpty()) {
            return;
        }
        if (items.size() < columns.size()) {
            List<String> keys = new ArrayList<>(columns.keySet());
            int lenItems = items.size();
            for (int i = 0; i < keys.size(); i++) {
                int index = i + 1;
                String col = keys.get(i);
                if (index < lenItems) {
                    results.put(col, items.get(index));
                } else {
                    JSONObject ansJs = new JSONObject();
                    ansJs.put("answer", "");
                    results.put(col, ansJs);
                }
                if (columns.get(col) instanceof String) {
                    continue;
                }
                SurveyColumn obj = ((SurveyColumn) columns.get(col));
                Integer gid = obj.getGid();
                if (!ObjectUtils.isEmpty(gid) && gidQuestionMap.get(gid.toString()) != null) {
                    JSONObject colJs = results.getJSONObject(col);
                    colJs.put("qtype", gidQuestionMap.get(gid.toString()).get("qtype"));
                    colJs.put("cid", gidQuestionMap.get(gid.toString()).get("cid"));
                }
            }
        } else {
            TreeMap<String, Object> zipJs = handle(new ArrayList<>(columns.keySet()), items);
            for (String col : zipJs.keySet()) {
                Object item = zipJs.get(col);
                results.put(col, item);
                if (columns.get(col) instanceof String) {
                    continue;
                }
                SurveyColumn obj = ((SurveyColumn) columns.get(col));
                Integer gid = obj.getGid();
                if (!ObjectUtils.isEmpty(gid) && gidQuestionMap.get(gid.toString()) != null) {
                    JSONObject colJs = results.getJSONObject(col);
                    colJs.put("qtype", gidQuestionMap.get(gid.toString()).get("qtype"));
                    colJs.put("cid", gidQuestionMap.get(gid.toString()).get("cid"));
                }
            }
        }
    }

    private List<QstructsDto> getQstructList(String projectId, Integer releaseVer) {
        //做缓存
        return qdesGrpcClient.qdesListQstructWithq(projectId, releaseVer).stream().map(grpcConvertMapper::toQstructsDto).collect(Collectors.toList());
    }

    private TreeMap<String, Object> handle(List<String> list1, List<Map<String, Object>> list2) {
        return IntStream.range(0, list1.size()).collect(TreeMap::new, (m, i) -> m.put(list1.get(i), list2.get(i)), (m, n) -> {
        });
    }

    private LocalDateTime googleTimestampToLocalDateTime(Timestamp timestamp) {
        if (timestamp == null || timestamp.getSeconds() < 0) {
            return null;
        }
        Date date = new Date(timestamp.getSeconds() * 1000);
        Instant instant = date.toInstant();
        ZoneId zoneId = ZoneId.systemDefault();
        return instant.atZone(zoneId).toLocalDateTime();
    }

    private Timestamp localDateTimetoGoogleTimestamp(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        return Timestamps.fromMillis(localDateTimeToDate(localDateTime).getTime());
    }

    private Date localDateTimeToDate(LocalDateTime time) {
        ZoneId zoneId = ZoneId.systemDefault();
        ZonedDateTime zdt = time.atZone(zoneId);
        return Date.from(zdt.toInstant());
    }

    private String secondsConvert2Hms(long seconds) {
        return DateUtil.formatBetween(seconds * 1000, BetweenFormatter.Level.SECOND);
    }

    private Map<String, String> parseUserAgent(String userAgent) {
        UserAgent agent = UserAgentUtil.parse(userAgent);
        Map<String, String> data = new HashMap<>();
        data.put("browser", agent.getBrowser() + "/" + agent.getVersion());
        data.put("os", agent.getOs() + "/" + agent.getOsVersion());
        return data;
    }
}